using System;
using System.Collections.Generic;
using Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners;
using System.Reflection;
using System.IO;
using System.Text;

namespace SBPweb.Logging
{
	/// <summary>
	/// EntLib base class for file based custom trace listeners.
	/// </summary>
	public abstract class EntLibFileTraceListener : EntLibTraceListener
	{
		/// <summary>
		/// String mFilePath;
		/// </summary>
		protected String mFilePath;
		/// <summary>
		/// StreamWriter mStreamWriter;
		/// </summary>
		protected volatile StreamWriter mStreamWriter;

		/// <summary>
		/// bool mTemporary;
		/// </summary>
		protected bool mTemporary = false;

		/// <summary>
		/// LogFolder mLogFolder;
		/// </summary>
		protected LogFolder mLogFolder;
		/// <summary>
		/// ILogNameProvider mLogNameProvider;
		/// </summary>
		protected ILogNameProvider mLogNameProvider;

		/// <summary>
		/// bool mAppend;
		/// </summary>
		protected bool mAppend = true;
		/// <summary>
		/// bool mImmediateFlush;
		/// </summary>
        protected bool mImmediateFlush = false;

		/// <summary>
		/// Gets the (current) file path of the log file.
		/// </summary>
		public String FilePath
		{
			get
			{
				return mFilePath;
			}
		}

		/// <summary>
		/// Gets the associated <see cref="LogFolder"></see>.
		/// </summary>
		public LogFolder LogFolder
		{
			get
			{
				return mLogFolder;
			}
		}

		/// <summary>
		/// Gets the associated <see cref="ILogNameProvider"></see>.
		/// </summary>
		public ILogNameProvider LogNameProvider
		{
			get
			{
				return mLogNameProvider;
			}
		}

		/// <summary>
		/// Gets if appending is preferred over truncation.
		/// </summary>
		/// <remarks>
		/// The default is true.
		/// </remarks>
		public bool Append
		{
			get
			{
				return mAppend;
			}
		}

        /// <summary>
        /// Gets if immediate flush is required.
        /// </summary>
        /// <remarks>
        /// The default is false.
        /// </remarks>
        public bool ImmediateFlush
        {
            get
            {
                return mImmediateFlush;
            }
        }

		/// <summary>
		/// Begins a logging block which writes log into a temporary memory buffer rather then the usual destination stream.
		/// </summary>
		/// <remarks>
		/// This method forms a pair in conjunction with <see cref="EntLibFileTraceListener.EndTemporary"/>. Their role is to make logging
		/// continual while accessing the underlaying files for some reason. Every logging in a block will be forwarded to a temporary memory
		/// buffer which will be flushed into file as a result of calling <see cref="EntLibFileTraceListener.EndTemporary"/>.
		/// This method supports the EntLib infrastructure.
		/// </remarks>
		/// <exception cref="System.InvalidOperationException">This block is nested into an other.</exception>
		protected internal void BeginTemporary()
		{
			if (mTemporary)
				throw new InvalidOperationException("Can not nest temporary blocks!");

			if (mStreamWriter != null)
				lock (mStreamWriter)
				{
					Close();

					mStreamWriter = new StreamWriter(new MemoryStream());
				}

			mTemporary = true;
		}

		/// <summary>
		/// Ends a logging block which was created by BeginTemporary(). It writes the temporary memory buffer into a newly initialized file destination.
		/// </summary>
		/// <remarks>
		/// This method forms a pair in conjunction with <see cref="EntLibFileTraceListener.BeginTemporary"/>. Their role is to make logging
		/// continual while accessing the underlaying files for some reason. Every logging in a block will be forwarded to a temporary memory
		/// buffer which will be flushed into file as a result of calling this method.
		/// This method supports the EntLib infrastructure.
		/// </remarks>
		/// <exception cref="System.InvalidOperationException">Called before <see cref="EntLibFileTraceListener.BeginTemporary"/>.</exception>
		protected internal void EndTemporary()
		{
			if (!mTemporary)
				throw new InvalidOperationException("EndTemporary can only be called after BeginTemporary!");

			if (mStreamWriter != null)
				lock (mStreamWriter)
				{
					if (mStreamWriter != null)
					{
						// Open will reinitialize mStreamWriter - we have to protect that reference from GC because it's Close can close
						// ms which is important to us
						StreamWriter sw = mStreamWriter;
						MemoryStream ms = mStreamWriter.BaseStream as MemoryStream;

						Open();

						mStreamWriter.Write(Encoding.UTF8.GetChars(ms.GetBuffer()));
					}
					else
					{
						Open();
					}
				}

			mTemporary = false;
		}

		/// <summary>
		/// Generates a new log file path into mFilePath.
		/// </summary>
		/// <remarks>
		/// Generates a new file name using the related objects (<see cref="LogFolder"/> and <see cref="ILogNameProvider"/>). If at least one
		/// of the related objects is not set then it set's EntLibFileTraceListener.FileName to be null.
		/// This method supports the EntLib infrastructure.
		/// </remarks>
		protected internal void GenerateFilePath()
		{
			mFilePath = mLogNameProvider != null && mLogFolder != null ? mLogNameProvider.GenerateName(mLogFolder) : mFilePath = null;
		}

		/// <summary>
		/// Stores important related object references while suspends and reinitializes logging if necessary.
		/// </summary>
		/// <remarks>
		/// Can be used to change the underlying <see cref="LogFolder"/> on the fly.
		/// </remarks>
		/// <param name="LogFolder">Log folder to use.</param>
		/// <param name="LogNameProvider">Name provider to use.</param>
		/// <exception cref="System.ArgumentNullException"> The related LogFolder or ILogNameProvider is null.</exception>
		public virtual void SetRelatedObjects(LogFolder LogFolder, ILogNameProvider LogNameProvider)
		{
			if (LogFolder == null)
				throw new ArgumentNullException("LogFolder");

			if (LogNameProvider == null)
				throw new ArgumentNullException("LogNameProvider");

			mLogFolder = LogFolder;
			mLogNameProvider = LogNameProvider;

			mFilePath = mLogNameProvider.GenerateName(mLogFolder);

			if (mStreamWriter != null)
			{
				BeginTemporary();

				EndTemporary();
			}
			else
			{
				Open();
			}
		}

		/// <summary>
		/// Called by <see cref="LogManager"></see> to initialize related objects.
		/// </summary>
		/// <remarks>
		/// This method makes it possible to instantiate related classes using attributes.
		/// By calling this the <see cref="LogManager"/> transforms this instance to be initialized and removes it from the collection
		/// of uninitialized listeners and adds it to the collection of initialized trace listeners.
		/// The initialization process can be started by calling <see cref="LogManager.Initialize()"/>.
		/// This method supports the EntLib infrastructure.
		/// </remarks>
		/// <exception cref="System.ArgumentException">Folder does not exists or ArchivingMethod is not <see cref="ArchivingMethod.None"/> and ArchiveFolder does not exists.</exception>
		/// <exception cref="System.ArgumentNullException">ArchivingMethod is not <see cref="ArchivingMethod.None"/> and ArchiveFolder does not exists.</exception>
		protected internal override void InitializeRelatedObjects()
		{
			#region Initializing and attaching an appropiate LogFolder instance

			// Caching TraceListener attributes
			String folder;
			try
			{
				folder = this.Attributes["Folder"] as String;
			}
			catch
			{
				// Exit if no folder has been specified (that is the case when it is instantiated from code)
				return;
			}

			String archivefolder;
			try
			{
				archivefolder = this.Attributes["ArchiveFolder"] as String;
			}
			catch
			{
				archivefolder = "";
			}

			int archivingmethod = 0;
			try
			{
				archivingmethod = int.Parse(this.Attributes["ArchivingMethod"] as String);
				if (archivingmethod < 0 || archivingmethod > 4)
					archivingmethod = 0;
			}
			catch { }

			long maxallowedage = -1;
			try
			{
				maxallowedage = long.Parse(this.Attributes["MaxAllowedAge"] as String);
			}
			catch { }

			long autoarchivinginterval = -1;
			try
			{
				autoarchivinginterval = long.Parse(this.Attributes["AutoArchivingInterval"] as String);
			}
			catch { }

			// Attaching LogFolder instance

			bool createfolder = false;
			try
			{
				createfolder = bool.Parse(this.Attributes["AutoCreateFolder"] as String);
			}
			catch { }

			mLogFolder = new LogFolder(
				folder,
				archivefolder,
				(ArchivingMethod)archivingmethod, maxallowedage == -1 ? NamedArchivingIntervals.OneWeek : new TimeSpan(maxallowedage * 10000000L),
				autoarchivinginterval == -1 ? NamedArchivingIntervals.OneDay : new TimeSpan(autoarchivinginterval * 10000000L),
				createfolder);

			#endregion

			#region Initializing and attaching appropiate ILogNameProvider descendant

			// Caching TraceListener attributes
			String nameproviderassembly = this.Attributes["NameProviderAssembly"] as String;
			String nameprovidertype = this.Attributes["NameProviderType"] as String;

			try
			{
				Assembly assembly = Assembly.LoadFile(Path.GetFullPath(nameproviderassembly));
				mLogNameProvider = Activator.CreateInstance(assembly.GetType(nameprovidertype)) as ILogNameProvider;
			}
			catch
			{
				mLogNameProvider = new Log4NetNameProvider();
			}

			#endregion

			#region Initializing other attributes

			try
			{
				mAppend = bool.Parse(this.Attributes["Append"] as String);
			}
			catch { }

            try
            {
                mImmediateFlush = bool.Parse(this.Attributes["ImmediateFlush"] as String);
            }
            catch { }

			try
			{
				LogXmlFormatter.TimestampFormat = this.Attributes["LogXmlTimeStampFormat"] as String;
			}
			catch { }

			try
			{
				Log4NetNameProvider.Log4NetNameTemplate = this.Attributes["LogXmlFileNameFormat"] as String;
			}
			catch { }

			#endregion

			SetRelatedObjects(mLogFolder, mLogNameProvider);
		}

		/// <summary>
		/// Returns the list of attributes supported by this <see cref="CustomTraceListener"></see>.
		/// Supports Enterprise Library framework.
		/// </summary>
		/// <returns>Returns the list of attributes supported by this <see cref="CustomTraceListener"></see>.</returns>
		protected override string[] GetSupportedAttributes()
		{
			List<String> result = new List<String>(base.GetSupportedAttributes());

			result.Add("Folder");
			result.Add("ArchivePath");
			result.Add("AutoCreateFolder");
			result.Add("ArchivingMethod");
			result.Add("MaxAllowedAge");
			result.Add("AutoArchivingInterval");
			result.Add("NameProviderAssembly");
			result.Add("NameProviderType");
			result.Add("Append");
            result.Add("ImmediateFlush");
			result.Add("LogXmlTimeStampFormat");
			result.Add("LogXmlFileNameFormat");

			return result.ToArray();
		}

		/// <summary>
		/// Opens a listener.
		/// </summary>
		/// <remarks>
		/// Must be overriden.
		/// This method supports the EntLib infrastructure.
		/// </remarks>
		public abstract void Open();
		/// <summary>
		/// Closes a listener.
		/// </summary>
		/// <remarks>
		/// Must be overriden.
		/// This method supports the EntLib infrastructure.
		/// </remarks>
		public abstract override void Close();

		/// <summary>
		/// Writes a message to the log.
		/// </summary>
		/// <param name="message">Formatted message to write.</param>
		public override void Write(string message)
		{
			if (mStreamWriter != null)
				lock (mStreamWriter)
				{
					// If the previous locker was a Close then it is null now.
                    if (mStreamWriter != null)
                    {
                        mStreamWriter.Write(message);

                        if (ImmediateFlush)
                        {
                            mStreamWriter.Flush();
                        }
                    }
				}
		}

		/// <summary>
		/// Writes a new message line to the log.
		/// </summary>
		/// <param name="message">Formatted message to write.</param>
		public override void WriteLine(string message)
		{
			if (mStreamWriter != null)
				lock (mStreamWriter)
				{
					// If the previous locker was a Close then it is null now.
                    if (mStreamWriter != null)
                    {
                        mStreamWriter.WriteLine(message);

                        if (ImmediateFlush)
                        {
                            mStreamWriter.Flush();
                        }
                    }
				}
		}

		/// <summary>
		/// Parameterless constructor in order to support ObjectBuilder.
		/// </summary>
		public EntLibFileTraceListener()
			: base()
		{
		}

		/// <summary>
		/// Initializes a new instance of <see cref="EntLibTraceListener"/> class.
		/// </summary>
		/// <remarks>
		/// <see cref="EntLibTraceListener"/> is an abstract class so exact instantiation of this class is not possible.
		/// </remarks>
		/// <param name="LogFolder"></param>
		/// <param name="LogNameProvider"></param>
		public EntLibFileTraceListener(LogFolder LogFolder, ILogNameProvider LogNameProvider)
			: base()
		{
			SetRelatedObjects(LogFolder, LogNameProvider);
		}
	}
}